//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Extensibility;
using OpenLS.Spreadsheet.StandardAddIn.Statistics;

namespace OpenLS.Spreadsheet.AnalysisToolPak
{
    [AddInFunctions]
    public static class EngineeringFunctions
    {
        public static object BesselI(double x, int n)
        {
            return BesselFunctions.BesselI(x, n);
        }

        public static object BesselJ(double x, int n)
        {
            return BesselFunctions.BesselJ(x, n);
        }

        public static object  BesselK(double x, int i)
        {
            return BesselFunctions.BesselK(x, i);
        }

        public static object BesselY(double x,int n)
        {
            return BesselFunctions.BesselY(x, n);
        }

        public static double Bin2Dec(string number)
        {
            number = number.Trim();
            if (number.Length > 10)
                throw new ErrorException(Errors.NumError);
            long result =  Base.Base2.Parse(number);
            if (result > 511)
                result = -1024 + result;
            Contract.Assert(result <= 511);
            Contract.Assert(result >= -512);
            return result;
        }

        public static object Bin2Hex(string number, [Optional(0d)] double places)
        {
            number = number.Trim();
            if (number.Length > 10)
                return Errors.NumError;
            long num = (long) Base.Base2.Parse(number);
            if (num > 511)
                num = -1024 + num;
            if (num < 0)
                num = 0xFFFFFFFFFF + (num + 1);
            if (num < 0)
                return Errors.NumError;
            if (num > 0xFFFFFFFFFF)
                return Errors.NumError;

            return Base.Base16.GetValue(num, places);
        }

        public static object Bin2Oct(string number, [Optional(0d)] double places)
        {
            long num = (long) Base.Base2.Parse(number);
            if (num > 511)
                num = -1024 + num;
            if (num < 0)
                num = 0x3FFFFFFF + (num + 1);
            if (num < 0)
                return Errors.NumError;
            if (num > 0x3FFFFFFF)
                return Errors.NumError;

            return Base.Base8.GetValue(num, places);
        }

        public static object Convert([TreatNullsAsZero] double number, string from_unit,
                                      string to_unit)
        {
            Contract.Requires(to_unit != null);
            Contract.Requires(from_unit != null);
            UnitBase fromUnit = Units.GetUnit(from_unit);
            UnitBase toUnit = Units.GetUnit(to_unit);
            if (fromUnit == null || toUnit == null)
                return Errors.NaError;
            return fromUnit.ConvertTo(number, toUnit);
        }

        public static string Dec2Bin([TreatNullsAsZero] double number, [Optional(0d)] double places)
        {
            Contract.Ensures(number == Bin2Dec(Contract.Result<string>()));
            long l = (long) number;
            if (l < -512 || l >= 512)
                throw new ErrorException(Errors.NumError);
            if (l < 0)
                l = 1024 + l;
            return Base.Base2.GetValue(l, places);
        }

        public static string Dec2Hex([TreatNullsAsZero] double number, [Optional(0d)] double places)
        {
            Contract.Ensures(number == Hex2Dec(Contract.Result<string>(), places));
            long n = (long) number;
            if (n < 0)
                n = 0xFFFFFFFFFF + (n + 1);
            if (n < 0)
                throw new ErrorException( Errors.NumError);
            if (n > 0xFFFFFFFFFF)
                throw new ErrorException(Errors.NumError);
            return Base.Base16.GetValue(n, places);
        }


        public static string Dec2Oct([TreatNullsAsZero] double number, [Optional(0d)] double places)
        {
            Contract.Ensures(number == Oct2Dec(Contract.Result<string>(), places));
            long n = (long)number;
            if (n < 0)
                n = 0x3FFFFFFF + (n + 1);
            if (n < 0)
                throw new ErrorException(Errors.NumError);
            if (n > 0x3FFFFFFF)
                throw new ErrorException( Errors.NumError);
            return Base.Base8.GetValue(n, places);
        }

        public static object Delta([TreatNullsAsZero] double number1, [Optional(0d)] double number2)
        {
            if (number1 == number2)
                return 1;
            return 0;
        }


        [AddInFunction(Name="ERF.PRECISE")]
        public static object Erf_Precise(double lower_limit, [Optional(double.NaN)] double upper_limit)
        {
            return Erf(lower_limit, upper_limit);

        }
        
        public static object Erf(double lower_limit, [Optional(double.NaN)] double upper_limit)
        {
            double actualLower;
            double actualUpper;
            if (double.IsNaN(upper_limit))
            {
                actualLower = 0;
                actualUpper = lower_limit;
            }
            else
            {
                actualLower = lower_limit;
                actualUpper = upper_limit;
            }
            if (actualLower < 0 || actualUpper < 0)
                return Errors.NumError;
            return Statistician.ErrorFunction(actualUpper) - Statistician.ErrorFunction(actualLower);
        }

        [AddInFunction(Name="ERFC.PRECISE")]
        public static object ErfC_precise (double x)
        {
            return ErfC(x);
        }
        public static object ErfC(double x)
        {
            if (x < 0)
                return Errors.NumError;
            return 1 - Statistician.ErrorFunction(x);
        }

        public static object GeStep(double number, [Optional(0d)] double step)
        {
            if (number >= step)
                return 1;
            return 0;
        }

        public static object
            Hex2Bin(string number, [Optional(0d)] double places)
        {
            var l = Base.Base16.Parse(number);
            if (l >= 0x8000000000)
                l = l - 0xFFFFFFFFFF - 1;

            if (l < 0)
                l = 1024 + l;

            return Base.Base2.GetValue(l, places);
        }

        public static double Hex2Dec(string number, [Optional(0d)] double places)
        {
            long l =  Base.Base16.Parse(number);
            if (l >= 0x8000000000)
                l = l - 0xFFFFFFFFFF  - 1;
            return (double)l;

        }

        public static object
            Hex2Oct(string number, [Optional(0d)] double places)
        {
            var l = Base.Base16.Parse(number);
            if (l >= 0x8000000000)
                l = l - 0xFFFFFFFFFF  - 1;
            if (l < 0)
                l = 0x3FFFFFFF + (l + 1);
            if (l < 0)
                return Errors.NumError;
            if (l > 0x3FFFFFFF)
                return Errors.NumError;

           
            return Base.Base8.GetValue(l, places);
        }

        public static object ImAbs(Complex inumber)
        {
            return inumber.Module;
        }

        public static object Imaginary(Complex inumber)
        {
            return inumber.Imaginary;
        }

        public static object ImArgument(Complex inumber)
        {
            return inumber.Theta;
        }

        public static object ImCos(Complex inumber)
        {
            return ComplexMath.Cosine(inumber);
        }

        public static object ImDiv(Complex inumber1, Complex inumber2)
        {
            if (DifferentSuffix(inumber1, inumber2))
                return Errors.NumError;
            if (inumber2.Module == 0)
                return Errors.NumError;
            return inumber1/inumber2;
        }

        public static object ImExp(Complex inumber)
        {
            return ComplexMath.Exp(inumber);
        }

        public static object  ImLog10(Complex inumber)
        {
            var result = ComplexMath.Ln(inumber);
            double factor = Math.Log(Math.E, 10);
            result = result * factor;
            return result;
        }

        public static object  ImLog2(Complex inumber)
        {
            var result = ComplexMath.Ln(inumber);
            double factor = Math.Log(Math.E, 2);
            result = result*factor;
            return result;
        }

        public static object ImPower(Complex inumber, double number)
        {
            return ComplexMath.Pow(inumber, number);
        }

        public static object ImProduct(Complex inumber1, Complex inumber2)
        {
            if (DifferentSuffix(inumber1, inumber2))
                return Errors.NumError;
            return inumber1*inumber2;
        }

        private static bool DifferentSuffix(Complex z1, Complex z2)
        {
            return z1.Suffix != z2.Suffix && z1.Imaginary != 0 && z2.Imaginary != 0;
        }

        public static object ImReal(Complex inumber)
        {
            return inumber.Real;
        }

        public static object ImSin(Complex inumber)
        {
            return ComplexMath.Sin(inumber);
        }

        public static object ImSqrt(Complex inumber)
        {
            return ComplexMath.Sqrt(inumber);
        }

        public static object ImSub(Complex inumber1, Complex inumber2)
        {
            if (DifferentSuffix(inumber1, inumber2))
                return Errors.NumError;

            return inumber1 - inumber2;
        }

        public static object ImSum(params Complex[] inumbers)
        {
            bool first = true;
            Complex result = new Complex();
            foreach (Complex c in inumbers)
            {
                if (first)
                {
                    result = c;
                    first = false;
                }
                else
                {
                    if (DifferentSuffix(result, c))
                        return Errors.NumError;
                    result = result + c;
                }
            }

            return result;
        }

        public static object
            Oct2Bin(string number, [Optional(0d)] double places)
        {
            var d = Oct2Dec(number, places);
            var result = Dec2Bin(d, places);
            return result;
            //\\return Base.Base2.GetValue(Base.Base8.Parse(number), places);
        }

        public static double Oct2Dec(string number, [Optional(0d)] double places)
        {
            long result = 
                Base.Base8.Parse(number);
            if (result >= 0x1FFFFFFF)
            result = result - 0x3FFFFFFF - 1;

            return result;

        }

        public static string Oct2Hex(string number, [Optional(0d)] double places)
        {
            var d = Oct2Dec(number, places);
            var result = Dec2Hex(d, places);
            return result;
        }


        public static object Complex(double real_num, double i_num, [Optional("i")] string suffix)
        {
            switch (suffix)
            {
                case "i":
                case "j":
                    break;
                default:
                    return Errors.ValueError;
            }
            return new Complex(real_num, i_num, suffix[0]);
        }

        public static object ImLn(Complex inumber)
        {
           // return new Complex(Math.Log(inumber.Module), Math.Atan2(inumber.Real, inumber.Imaginary), inumber.Suffix);
            //return ComplexMath.Log(inumber);
            return ComplexMath.Ln(inumber);
        }

        public static object ImConjugate(Complex inumber)
        {
            return inumber.Conjugate();
        }
    }
}